/**
 * 
 */
package wordEvo.solo;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.UnknownHostException;

import whipstaff.data.WhipPacket;
import whipstaff.tools.IOHelper;
import whipstaff.wordevo.data.Result;
import whipstaff.wordevo.lists.ChildList;
import whipstaff.wordevo.lists.GoalList;
import whipstaff.wordevo.words.ChildWord;
import whipstaff.wordevo.words.GoalWord;

/**
 * @author Jim Buck, Whipstaff Productions
 * @since August 9, 2011
 * @updated August 19, 2011
 */
public class WordEvo {
	final static String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

	final static int charsLength = chars.length();

	final static String gWord = "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG.";
	final static String[] defWordList = { "TIME", "PERSON", "YEAR", "WAY",
			"DAY", "THING", "MAN", "WORLD", "LIFE", "HAND", "PART", "CHILD",
			"EYE", "WOMAN", "PLACE", "WORK", "WEEK", "CASE", "POINT",
			"COMPANY", "NUMBER", "GOOD", "NEW", "FIRST", "LAST", "LONG",
			"GREAT", "LITTLE", "OWN", "OTHER", "OLD", "RIGHT", "BIG", "HIGH",
			"DIFFERENT", "SMALL", "LARGE", "NEXT", "EARLY", "YOUNG",
			"IMPORTANT", "FEW", "PUBLIC", "BAD", "SAME", "ABLE", "BE", "HAVE",
			"DO", "SAY", "GET", "MAKE", "GO", "KNOW", "TAKE", "SEE", "COME",
			"THINK", "LOOK", "WANT", "GIVE", "USE", "FIND", "TELL", "ASK",
			"SEEM", "FEEL", "TRY", "LEAVE", "CALL" };

	static GoalList wordList = new GoalList();
	static ChildList children = new ChildList();

	static int listLength;

	static int instances = 10;
	static int maxChildren = 20;

	static GoalWord goalWord;

	static int difference;
	static long generations;

	static long startTime;
	static long endTime;
	static long totalTrials;
	static long totalGenerations;
	static long totalChildren;
	static double totalTime;
	static long progStart;
	static long progEnd;

	static long charCount[];

	final static String newLine = System.getProperty("line.separator");
	static String inputFileName;
	static String outputString;

	/**
	 * @param args
	 *            Input file to be parsed and read
	 */
	public static void main(String[] args) {
		progStart = System.nanoTime();
		totalTrials = 0;
		totalGenerations = 0;
		totalTime = 0;

		charCount = new long[chars.length()];
		outputString = "";

		if (args.length == 3) {
			instances = Integer.parseInt(args[1]);
			maxChildren = Integer.parseInt(args[2]);
			handleInputFile(args[0]);
		} else if (args.length == 2) {
			instances = Integer.parseInt(args[0]);
			maxChildren = Integer.parseInt(args[1]);
			setWordList();
		} else if (args.length == 1) {
			handleInputFile(args[0]);
		} else {
			setWordList();
		}

		wordList.sort();
		listLength = wordList.size();

		for (int word = 0; word < listLength; word++) {
			long beginTime = System.nanoTime();

			System.out.print("Processing: " + wordList.get(word) + "("
					+ wordList.get(word).getFreq() + ")" + " \t\t("
					+ (word + 1) + "/" + listLength + ")");
			outputString += wordList.get(word) + "("
					+ wordList.get(word).getFreq() + ")" + "\t\t(" + (word + 1)
					+ "/" + listLength + ")";

			for (int i = 0; i < instances * wordList.get(word).getFreq(); i++) {
				totalTrials++;

				goalWord = new GoalWord(wordList.get(word).toString());

				startTime = System.nanoTime();
				beginEvolution();
				endTime = System.nanoTime();

				handleResults(i);
			}

			System.out.println("\t - "
					+ printTime(System.nanoTime() - beginTime));
			outputString += "\t - " + printTime(System.nanoTime() - beginTime)
					+ newLine;

			// handleResultPacket();
		}
		progEnd = System.nanoTime();

		printFinalStats();

		printCharStats();

		saveToFile();
	}

	private static void handleInputFile(String arg) {
		try {
			// Open the file that is the first
			// command line parameter
			FileInputStream reader = new FileInputStream(arg);
			String chr;
			String str;

			// Read File Line By Line
			while (reader.available() > 0) {
				str = "";
				do {
					chr = "" + (char) reader.read();

					if (chars.indexOf(chr.toUpperCase()) == -1) {
						break;
					} else
						str += chr;

				} while (reader.available() > 0);

				if (!str.isEmpty() && str.length() > 2) {
					wordList.add(new GoalWord(str.toUpperCase()));
				}

			}

			reader.close();
		} catch (Exception e) {
			System.err.println("ERROR: " + e.getMessage());
		}
	}

	private static void setWordList() {
		wordList.clear();

		for (String s : defWordList) {
			wordList.add(new GoalWord(s));
		}
	}

	private static void beginEvolution() {

		ChildWord guess = new ChildWord(goalWord);

		generations = 0;

		while (!goalWord.isSameString(guess)) {
			children.clear();

			for (int i = 0; i < maxChildren; i++) {
				ChildWord newChild = new ChildWord(guess);
				children.add(newChild);

				if (newChild.getFitness() == 0) {
					totalChildren += maxChildren - i;
					break;
				} else {
					totalChildren++;
				}
			}

			generations++;

			guess = children.getBestChild();
			charCount[(int) guess.getLetterIndex()]++;
		}
	}

	private static void handleResults(int index) {
		totalGenerations += generations;

		double time = formatTime(endTime - startTime);
		totalTime += time;
	}

	private static double formatTime(long nanos) {
		return (double) (nanos) / (Math.pow(10, 9));
	}

	private static double round(double num, double f) {
		double factor = Math.pow(10.0, f);

		return ((double) Math.round(factor * num)) / factor;
	}

	private static String printTime(long nanos) {
		double seconds = formatTime(nanos);

		int hours = 0;
		int minutes = (int) seconds / 60;

		seconds -= minutes * 60;
		if (minutes > 59) {
			hours = (int) minutes / 60;
			minutes -= hours * 60;
		}

		return hours + "h " + minutes + "m "
				+ round(seconds, (seconds > 1 ? 2 : 4)) + "s ";
	}

	private static void printFinalStats() {
		String temp = "";

		// Output to file
		temp += String.format("Total Words: %,d", listLength) + newLine;
		temp += String.format("Trials per Occurrence: %,d", instances)
				+ newLine;
		temp += String.format("Maximum Children per Generation: %,d",
				maxChildren)
				+ newLine + newLine;

		temp += "Total Time: " + printTime(progEnd - progStart) + newLine;
		temp += String.format("Total Generations: %,d", totalGenerations)
				+ newLine;
		temp += String.format("Total Children: %,d", totalChildren) + newLine;

		temp += String.format("%nAverage Generations per Trial: %,d",
				(int) (totalGenerations / totalTrials))
				+ newLine;
		temp += String.format("Average Generations per Second: %,d",
				(int) (totalGenerations / totalTime))
				+ newLine;
		temp += String.format("Average Children per Second: %,d",
				(int) (maxChildren * totalGenerations / totalTime))
				+ newLine + newLine;

		outputString = temp
				+ "---------------------------------------------------------------------"
				+ newLine
				+ outputString
				+ newLine
				+ "---------------------------------------------------------------------"
				+ newLine;

		// Output to System
		System.out.println("\n\n********************************");

		System.out.format("Total Words: %,d%n", listLength);
		System.out.format("Trials per Occurrence: %,d%n", instances);
		System.out.format("Maximum Children per Generation: %,d%n%n",
				maxChildren);
		System.out.println("Total Trials: " + totalTrials);

		System.out.println("Total Time: " + printTime(progEnd - progStart));
		System.out.format("Total Generations: %,d%n", totalGenerations);
		System.out.format("Total Children: %,d%n", totalChildren);

		System.out.format("%nAverage Generations per Trial: %,d%n",
				(int) (totalGenerations / totalTrials));
		System.out.format("Average Generations per Second: %,d%n",
				(int) (totalGenerations / totalTime));
		System.out.format("Average Children per Second: %,d%n%n",
				(int) (maxChildren * totalGenerations / totalTime));
	}

	private static void printCharStats() {
		double percent = 0.0;

		for (int i = 0; i < chars.length(); i++) {
			System.out.print(chars.charAt(i) + "\t||\t" + charCount[i]
					+ "\t|\t");
			outputString += chars.charAt(i) + "\t||\t" + charCount[i] + "\t|\t";
			percent = (double) Math
					.round(1000 * ((double) charCount[i] / totalGenerations)) / 10;

			for (int star = 0; star < (int) (percent * 4); star++) {
				System.out.print("]]");
				outputString += "]]";
			}

			System.out.println(" " + percent + "%");
			outputString += " " + percent + "%" + newLine;
		}
	}

	private static void saveToFile() {
		File outFile = null;
		String fileName = "";
		int index = -1;

		try {
			do {
				index++;
				if (index < 10)
					fileName = "Outputs/" + listLength + "w_" + instances
							+ "i_" + maxChildren + "c_0" + index + ".txt";
				else
					fileName = "Outputs/" + listLength + "w_" + instances
							+ "i_" + maxChildren + "c_" + index + ".txt";

				outFile = new File(fileName);

			} while (!outFile.createNewFile());
			
		} catch (IOException e) {
			System.err.println("ERROR: " + e.getMessage());
			e.printStackTrace();
		}

		try {
			FileWriter fWriter = new FileWriter(fileName);
			BufferedWriter out = new BufferedWriter(fWriter);
			out.write(outputString);
			out.flush();
			out.close();
			System.out.println("\nThe results can be found in "
					+ outFile.getAbsolutePath());
		} catch (IOException e) {
			System.err.println("ERROR: " + e.getMessage());
			e.printStackTrace();
		}

	}

	private static void handleResultPacket() {
		Result resultPacket = new Result(goalWord, instances, maxChildren,
				totalGenerations, totalChildren, endTime - startTime,
				totalTrials, charCount);

		try {
			IOHelper.transmitObject("localhost", 6014, new WhipPacket(resultPacket,"", "", ".temp"));
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
